#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <inttypes.h>
#include <time.h>

// 信号量使用目前有个问题，一个进程锁住然后崩溃或直接退出，其他进程无法使用锁。
// SETVAL和semop应该是操作同一个变量sem，
// 如果进程A中SETVAL=1，然后P操作,此时sem=0；
// 而B进程中再次SETVAL=1，然后sem=1.
// 如果A进行V操作，sem=2.同时可进入的进程编程2，就出问题了。

// 可能需要这样处理会不会好一点。A进程作为主进程，B进程作为子进程。A启动时，删除信号量，初始化信号量。再启动B进程。B进程仅仅获得信号量。
// 如果B进程挂了，A重新删除信号量，初始化信号量，再启动B进程，B进程仅仅获得信号量。
// 如果A挂了，看门狗啥的让它启动？

struct semBaesStrcut
{
    int semid;
    // int semnumber;
    // 可以P操作的数量。
    int pnumber;
    int master;
    key_t semKey;
};
#define semInitData \
    (struct semBaesStrcut) { -1, -1, -1, -1 }
int semBaesQuit(struct semBaesStrcut *semopt)
{
    // if (!semopt)
    // {
    //     perror("semBaesQuit is null");
    //     return -1;
    // }
    // if (semopt->semid >= 0)
    // {
    //     semctl(semopt->semid, 0, IPC_RMID);
    // }
    // *semopt = semInitData;
    return 0;
}
int semBaesInit(struct semBaesStrcut *semopt, int pnum, int ismast, key_t inkey)
{
    if (!semopt)
    {
        perror("semBaesInit is null");
        return -1;
    }
    semopt->semKey = inkey;
    semopt->master = ismast;
    semopt->pnumber = pnum;
    // __nsems is sem numbers.
    semopt->semid = semget(semopt->semKey, 1, 0666 | IPC_CREAT);
    if(semopt->semid < 0)
    {
        printf("semBaesInit semget err: key:%d,ismater:%d,pnumber:%d\n", semopt->semKey, semopt->master, semopt->pnumber);
    }
    printf("semBaesInit: key:%d,ismater:%d,pnumber:%d,id:%d\n", semopt->semKey, semopt->master, semopt->pnumber, semopt->semid);
    // if (!semopt->master)
    // {
    //     return semopt->semid;
    // }
    // 两个进程都配置了，没有互锁效果。
    // set p opt can inter numbers
    if(!semopt->master)
    {
        return semopt->semid;
    }
    int tmpnumber = -1;
    int tmpflag = semctl(semopt->semid, 0, GETVAL, &tmpnumber);
    if(tmpflag < 0)
    {
        printf("semBaesInit GETVAL,%d\n", tmpflag);
    }
    printf("semBaesInit tmpnumber,%d\n", tmpnumber);
    if (tmpnumber >= 0)
    {
        printf("semBaesInit not cfg numb,%d\n", tmpnumber);
        return semopt->semid;
    }
    if (semctl(semopt->semid, 0, SETVAL, semopt->pnumber) == -1)
    {
        printf("semBaesInit semctl err: key:%d,ismater:%d,pnumber:%d\n", semopt->semKey, semopt->master, semopt->pnumber);
        return -1;
    }
    return semopt->semid;
}
int semBaesDoP(struct semBaesStrcut *semopt, int tryflag)
{
    if (!semopt)
    {
        perror("semBaesInit is null");
        return -2;
    }
    struct sembuf sops;
    sops.sem_num = 0; // 操作信号量集中的第一个信号量
    sops.sem_op = -1; // 执行P操作，
    if (tryflag)
    {
        sops.sem_flg = IPC_NOWAIT; // IPC_NOWAIT 不等, SEM_UNDO 阻塞
    }
    else
    {
        sops.sem_flg = SEM_UNDO; // IPC_NOWAIT 不等, SEM_UNDO 阻塞
    }
    // IPC_NOWAIT, 0ok, -1 fail
    int tmpflag = semop(semopt->semid, &sops, 1);
    // printf("p opt result=%d\n", tmpflag);
    return tmpflag;
}
int semBaesTryP(struct semBaesStrcut *semopt)
{
    return semBaesDoP(semopt, 1);
}
int semBaesWaitP(struct semBaesStrcut *semopt)
{
    return semBaesDoP(semopt, 0);
}
//
int semBaesV(struct semBaesStrcut *semopt)
{
    if (!semopt)
    {
        perror("semBaesInit is null");
        return -1;
    }
    struct sembuf sops;
    sops.sem_num = 0;                      // 操作信号量集中的第一个信号量
    sops.sem_op = 1;                       // 执行V操作，增加信号量的值 by 1
    sops.sem_flg = SEM_UNDO;               // IPC_NOWAIT 不等, SEM_UNDO 阻塞
    return semop(semopt->semid, &sops, 1); // 执行V操作，将信号量的值增加 by 1
}

int main(int argsize, const char **args)
{
    struct semBaesStrcut tmpsem = semInitData;
    if (argsize < 2)
    {
        perror("param input error");
        return -1;
    }
    int tmpmaster = atoi(args[1]);
    int tmpflag;
    printf("sem is master:%d\n", tmpmaster);
    do
    {
        tmpflag = semBaesInit(&tmpsem, 1, tmpmaster, 1234567);
        if (tmpflag >= 0)
        {
            break;
        }
        if (tmpmaster)
        {
            perror("sem master init fail");
            return -1;
        }
        usleep(200000);
    } while (tmpflag < 0);
    printf("sem init ok:%d\n", tmpsem.semid);
    uint64_t tmpsec = time(NULL);
    while (1)
    {
        if (time(NULL) - tmpsec >= 20)
        {
            break;
        }
        usleep(10000);
        if (semBaesTryP(&tmpsem) < 0)
        {
            // perror("sem p fail");
            continue;
        }
        printf("pid=%d, sem P ok:%d\n", getpid(), tmpsem.semid);
        sleep(1);
        semBaesV(&tmpsem);
    }
    semBaesQuit(&tmpsem);
    return 0;
}